package com.opencee.cloud.bpm.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.opencee.boot.db.mybatis.service.impl.SupperServiceImpl;
import com.opencee.cloud.bpm.constants.BpmFormWidget;
import com.opencee.cloud.bpm.entity.BpmFormTableEntity;
import com.opencee.cloud.bpm.mapper.BpmFormTableMapper;
import com.opencee.cloud.bpm.service.IBpmFormTableService;
import com.opencee.cloud.bpm.vo.BpmTableFiled;
import com.opencee.common.exception.BaseErrorException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @author liuyadu
 * @date 2022/4/24
 */
@Slf4j
@Service
public class IBpmFormTableServiceImpl extends SupperServiceImpl<BpmFormTableMapper, BpmFormTableEntity> implements IBpmFormTableService {


    /**
     * 创建流程表
     *
     * @param processId
     * @param processName
     * @param processKey
     * @param parentTableName 父级表名称
     * @param form
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createTable(String processId, String processKey, String processName, JSONObject form, String parentTableName) {
        JSONArray props = form.getJSONArray("list");
        if (props == null) {
            return;
        }
        Long tableId = IdWorker.getId();
        String tableName = String.format("t_%s", processKey);
        QueryWrapper<BpmFormTableEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BpmFormTableEntity::getTableName, tableName);
        if (this.baseMapper.selectCount(queryWrapper) > 0) {
            throw new BaseErrorException(String.format("建表失败:表名'%s' 数据已存在", tableName));
        }
        if (baseMapper.selectTableCount(tableName) > 0) {
            throw new BaseErrorException(String.format("建表失败:表名'%s' 物理表已存在", tableName));
        }
        List<BpmTableFiled> tableFields = new ArrayList<>();
        if (parentTableName != null) {
            BpmTableFiled filed = new BpmTableFiled();
            filed.setType("Long");
            filed.setName("parent_id");
            filed.setComment("主表id");
            tableFields.add(filed);
        }
        if (props != null) {
            Iterator<Object> iterator = props.iterator();
            while (iterator.hasNext()) {
                BpmTableFiled tableFiled = new BpmTableFiled();
                JSONObject prop = (JSONObject) iterator.next();
                String type = prop.getString("type");
                String model = prop.getString("model");
                String label = prop.getString("label");
                List<JSONObject> rules = new ArrayList<>();
                if (prop.containsKey("rules")) {
                    rules = prop.getJSONArray("rules").toJavaList(JSONObject.class);
                }
                BpmFormWidget widget = BpmFormWidget.getByType(type);
                if (widget == null) {
                    continue;
                }
                tableFiled.setWidget(widget);
                if (widget.getTableFiled()) {
                    JSONObject options = prop.getJSONObject("options");
                    Integer maxLength = options.getInteger("maxLength");
                    Integer precision = options.getInteger("precision");
                    if (maxLength == null) {
                        maxLength = widget.getMaxLength();
                    }
                    if (precision == null) {
                        precision = 0;
                    }
                    tableFiled.setType(widget.getFiledType());
                    tableFiled.setName(model);
                    tableFiled.setComment(label);
                    tableFiled.setMaxLength(maxLength);
                    tableFiled.setPrecision(precision);
                    tableFiled.setNotNull(rules.stream().anyMatch(r -> r.getBoolean("required")));
                    tableFields.add(tableFiled);
                } else {
                    // 创建子表
                    if (BpmFormWidget.batch.getType().equals(type)) {
                        String key = processKey + "_" + model;
                        String name = processName + "-" + label;
                        this.createTable(processId, key, name, prop, tableName);
                    }
                }
            }
        }
        if (CollectionUtils.isEmpty(tableFields)) {
            throw new BaseErrorException("建表失败,缺少可用字段");
        }
        String tableComment = processName;
        String ddl = this.buildCreateSQL(tableName, tableComment, tableFields);
        BpmFormTableEntity entity = new BpmFormTableEntity();
        entity.setId(tableId);
        entity.setProcessId(processId);
        entity.setTableName(tableName);
        entity.setTableComment(processName);
        entity.setParentTableName(parentTableName);
        entity.setDdl(ddl);
        this.save(entity);
        baseMapper.createTable(ddl);
    }


    private String buildCreateSQL(String tableName, String tableComment, List<BpmTableFiled> tableFields) {
        StringBuffer sqlBuffer = new StringBuffer();
        sqlBuffer.append(String.format("CREATE TABLE `%s` (\n", tableName));
        sqlBuffer.append("`id` bigint NOT NULL COMMENT '主键id',\n");
        tableFields.forEach(filed -> {
            BpmFormWidget widget = filed.getWidget();
            if (widget != null && StringUtils.hasText(widget.getColumnType())) {
                sqlBuffer.append(formatSql(filed)).append(",\n");
            }
        });
        sqlBuffer.append("`process_id` varchar(64)   NULL DEFAULT NULL COMMENT '绑定的流程ID',\n" +
                "`tenant_id` varchar(64)   NULL DEFAULT NULL COMMENT '租户id',\n" +
                "`deleted` smallint DEFAULT NULL COMMENT '是否已删除:0-未删除 1-已删除',\n" +
                "`create_time` datetime NOT NULL COMMENT '创建时间',\n" +
                "`update_time` datetime DEFAULT NULL COMMENT '更新时间',\n" +
                "`create_user_id` varchar(64)  DEFAULT NULL COMMENT '创建人',\n" +
                "`update_user_id` varchar(64)  DEFAULT NULL COMMENT '更新人',\n" +
                "PRIMARY KEY (`id`) USING BTREE,\n" +
                "INDEX `process_id`(`process_id`) USING BTREE,\n" +
                "INDEX `tenant_id`(`tenant_id`) USING BTREE\n");
        sqlBuffer.append(String.format(") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT ='%s';", tableComment));
        return sqlBuffer.toString();
    }

    /**
     * 更新表
     *
     * @param processId
     * @param processKey
     * @param oldProcessName
     * @param newProcessName
     * @param oldFrom
     * @param newForm
     * @param parentTableName
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateTable(String processId, String processKey, String oldProcessName, String newProcessName, JSONObject oldFrom, JSONObject newForm, String parentTableName) {
        JSONArray props = newForm.getJSONArray("list");
        if (props == null) {
            return;
        }
        String tableName = String.format("t_%s", processKey);
        if (baseMapper.selectTableCount(tableName) == 0) {
            throw new BaseErrorException(String.format("修改建表失败:表名'%s' 不存在", tableName));
        }
        if (oldFrom == null) {
            oldFrom = new JSONObject();
        }
        JSONArray oldProps = oldFrom.getJSONArray("list");
        List<BpmTableFiled> tableFields = new ArrayList<>();
        Map<String, JSONObject> oldMap = new HashMap<>(8);
        if (oldProps != null) {
            Iterator<Object> iterator = oldProps.iterator();
            while (iterator.hasNext()) {
                JSONObject prop = (JSONObject) iterator.next();
                String type = prop.getString("type");
                String model = prop.getString("model");
                BpmFormWidget widget = BpmFormWidget.getByType(type);
                if (widget == null) {
                    continue;
                }
                oldMap.put(model, prop);
            }
        }

        StringBuffer alter = new StringBuffer(String.format("ALTER TABLE `%s` ", tableName));
        if (props != null) {
            Iterator<Object> newIterator = props.iterator();
            while (newIterator.hasNext()) {
                JSONObject prop = (JSONObject) newIterator.next();
                String type = prop.getString("type");
                String model = prop.getString("model");
                String label = prop.getString("label");
                List<JSONObject> rules = new ArrayList<>();
                if (prop.containsKey("rules")) {
                    rules = prop.getJSONArray("rules").toJavaList(JSONObject.class);
                }
                JSONObject oldProp = oldMap.get(model);

                BpmFormWidget widget = BpmFormWidget.getByType(type);
                if (widget == null) {
                    continue;
                }
                if (widget.getTableFiled()) {
                    JSONObject options = prop.getJSONObject("options");
                    Integer maxLength = options.getInteger("maxLength");
                    Integer precision = options.getInteger("precision");
                    if (maxLength == null) {
                        maxLength = widget.getMaxLength();
                    }
                    if (precision == null) {
                        precision = 0;
                    }
                    BpmTableFiled tableFiled = new BpmTableFiled();
                    tableFiled.setType(widget.getFiledType());
                    tableFiled.setName(model);
                    tableFiled.setComment(label);
                    tableFiled.setMaxLength(maxLength);
                    tableFiled.setPrecision(precision);
                    tableFiled.setNotNull(rules.stream().anyMatch(r -> r.getBoolean("required")));
                    tableFiled.setWidget(widget);
                    if (oldProp == null) {
                        // 新增
                        alter.append("ADD COLUMN " + formatSql(tableFiled)).append(",");
                    } else {
                        String oldType = oldProp.getString("type");
                        String oldLabel = oldProp.getString("label");
                        if (!oldType.equals(type) || !oldLabel.equals(label)) {
                            // 修改
                            alter.append("MODIFY COLUMN " + formatSql(tableFiled)).append(",");
                        }
                    }
                    tableFields.add(tableFiled);
                } else {
                    // 创建子表
                    if (BpmFormWidget.batch.getType().equals(type)) {
                        String key = processKey + "_" + model;
                        String name = newProcessName + "-" + label;
                        String tName = String.format("t_%s", key);
                        if (baseMapper.selectTableCount(tName) == 0) {
                            // 不存在创建子表
                            this.createTable(processId, key, name, prop, tableName);
                        } else {
                            // 修改子表
                            this.updateTable(processId, key, name, name, oldProp, prop, tableName);
                        }
                    }
                }
            }
        }
        alter.append(String.format("COMMENT = '%s'", newProcessName)).append(";");
        System.out.println(alter.toString());
        QueryWrapper<BpmFormTableEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BpmFormTableEntity::getTableName, tableName);
        BpmFormTableEntity entity = new BpmFormTableEntity();
        entity.setTableComment(newProcessName);
        String ddl = this.buildCreateSQL(tableName, entity.getTableComment(), tableFields);
        entity.setDdl(ddl);
        this.update(entity, queryWrapper);
        this.baseMapper.alterTable(alter.toString());
    }


    private String formatSql(BpmTableFiled filed) {
        String sql = String.format("`%s` %s %s %s COMMENT '%s'", filed.getName(), String.format(filed.getWidget().getColumnType(), filed.getMaxLength(), filed.getPrecision()), filed.getNotNull() != null && filed.getNotNull() ? "NOT NULL" : "NULL", StringUtils.hasText(filed.getDefaultValue()) ? "DEFAULT " + filed.getDefaultValue() : "", filed.getComment());
        return sql;
    }
}
